
// 1.Promise是一个类
// 2. 三种状态 PENDING(默认) FULFILED（成功态） REJECTED(失败态)
// 3. 状态只能由默认态转变为成功态或者失败态，成功态和失败态之间不能相互转变
// 4. resolve和reject的结果会传入到then中的回调函数中
// 5. 如果是异步返回数据，需要用到发布订阅模式来执行
// 6. 每次then之后返回还是一个promise
// 7. 需要判断then中onFulfailed和onRejected返回的是普通值还是promise的情况
// 8. 利用setTimeout对7中返回的结果进行处理，不然拿不到promise2 模拟微任务
// 9. 如果在上一次的then的失败中返回一个普通值，也会走到下一次的then的成功中
// 10. x和promise2如果相同，就会循环调用
// 11. 如何判断promise是一个promise
// 12. 处理值的穿透问题
// 13. 上来就resolve一个promise,这已经不属于Promise/A+规范了，会在then的成功中拿到最终的普通值
// 14. 上来就reject一个promise,会在then的失败中拿到这个promise

const PENDING = 'PENDING'
const FULFILED = 'FULFILED'
const REJECTED = 'REJECTED'

/**
 * 判断是否promise的方法
 * @param {*} promise 
 * @returns 
 */
const isPromise = promise => {
    if ((typeof promise === 'object' && typeof promise !== null) || typeof promise === 'function') {
        return typeof promise.then === 'function'
    } else {
        return false
    }
}
/**
 * 链式调用的核心方法
 * @param {*} promise2 
 * @param {*} x 
 * @param {*} resolve 
 * @param {*} reject 
 * @returns 
 */
const resolvePromise = (promise2, x, resolve, reject) => {
    // 防止别人写的状态发送突变
    let called;
    if (promise2 === x) {
        return reject(new TypeError(`Chaining cycle detected for promise #<Promise>`))
    }
    if (isPromise(x)) {
        try {
            const then = x.then
            then.call(x, y => {
                // y可能是promise ,也可能是个普通值 递归调用
                if (called) return
                called = true
                resolvePromise(promise2, y, resolve, reject)
            }, r => {
                if (called) return
                called = true
                reject(r)
            })
        } catch (error) {
            if (called) return
            called = true
            reject(error)
        }
    } else {
        // 普通值直接返回
        // console.log('我又来到了这里')
        resolve(x)
    }

}
/**
 * promise及其周边方法的实现
 */
class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = '';
        this.reason = '';
        this.fulfiledCallbacks = [];
        this.rejectedCallbacks = [];
        const resolve = (value) => {
            if (value instanceof Promise) {
                // console.log(value.then)
                // console.log(resolve)
                return value.then(resolve, reject)
            }
            if (this.status === PENDING) {
                this.status = FULFILED
                this.value = value
                this.fulfiledCallbacks.forEach(v => v())
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.rejectedCallbacks.forEach(v => v())
            }
        }
        try {
            executor(resolve, reject)
        } catch (error) {
            console.log(error)
            reject(error)
        }
    }
    then(onFulfailed, onRejected) {
        // then(data=>{
        //    return data
        //})
        onFulfailed = typeof onFulfailed === 'function' ? onFulfailed : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
        const promise2 = new Promise((resolve, reject) => {
            if (this.status === FULFILED) {
                setTimeout(() => {
                    try {
                        const x = onFulfailed(this.value)
                        // console.log('x=>', x)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            }
            if (this.status === REJECTED) {
                // const x = onRejected(this.reason)
                // resolve(x)
                setTimeout(() => {
                    try {
                        console.log('1111', this.reason)
                        const x = onRejected(this.reason)
                        console.log('2222', x)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        console.log('error', error)
                        reject(error)
                    }

                }, 0)

            }
            if (this.status === PENDING) {
                this.fulfiledCallbacks.push(() => {
                    // TODOS AOP
                    setTimeout(() => {
                        try {
                            console.log('todo11', this.value)
                            const x = onFulfailed(this.value)
                            console.log('todo12', x)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            console.log('todo13', error)
                            reject(error)
                        }

                    }, 0)
                })
                this.rejectedCallbacks.push(() => {
                    // TODOS AOP
                    setTimeout(() => {
                        try {
                            console.log('todo21', this.reason)
                            const x = onRejected(this.reason)
                            console.log('todo22', x)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            console.log('todo23', error)
                            reject(error)
                        }
                    }, 0)
                })
            }
        })
        return promise2
    }
    catch(errorFn) {
        return this.then(null, errorFn)
    }
    static resolve(value) {
        return new Promise((resolve, reject) => {
            resolve(value)
        })
    }
    static reject(value) {
        return new Promise((resolve, reject) => {
            reject(value)
        })
    }
    static all(promiseArr) {
        return new Promise((resolve, reject) => {
            const result = []
            let sum = 0
            const processPromise = (key, value) => {
                result[key] = value
                // 解决异步并发的惯用伎俩
                if (++sum === promiseArr.length) {
                    resolve(result)
                }
            }

            for (let i = 0, len = promiseArr.length; i < len; i++) {
                const currentPromise = promiseArr[i]
                if (isPromise(currentPromise)) {
                    // 如果是promise，要获取到普通值，不说废话，直接调用then
                    currentPromise.then(data => {
                        processPromise(i, data)
                    }, reject)
                } else {
                    processPromise(i, currentPromise)
                }
            }
        })
    }
    static race(promiseArr) {
        return new Promise((resolve, reject) => {
            for (let i = 0, len = promiseArr.length; i < len; i++) {
                const currentPromise = promiseArr[i];
                if (isPromise(currentPromise)) {
                    currentPromise.then(resolve, reject)
                } else {
                    resolve(currentPromise)
                }
            }
        })
    }
    finally(callback) {
        return this.then((data) => {
            return Promise.resolve(callback()).then(() => data)
        }, (err) => {
            return Promise.resolve(callback()).then(() => { throw err })
        })
    }
    static allSettled(promiseArr) {
        return new Promise((resolve, reject) => {
            const result = []
            const len = promiseArr.length;
            for (let i = 0; i < len; i++) {
                const currentPromise = promiseArr[i];
                Promise.resolve(currentPromise).then(data => {
                    result.push({
                        status: 'fulfilled',
                        value: data
                    })
                    len -= 1;
                    if (len === 0) {
                        resolve(result)
                    }
                }, err => {
                    result.push({
                        status: 'rejected',
                        reason: err
                    })
                    len -= 1;
                    if (len === 0) {
                        resolve(result)
                    }
                })
            }
        })
    }
    static any(promiseArr) {
        return new Promise((resolve, reject) => {
            let sum = 0;
            const len = promiseArr.length;
            promiseArr.forEach((currentPromise) => {
                Promise.resolve(currentPromise).then(data => {
                    resolve(data)
                }, reason => {
                    sum++
                    if (sum === len) {
                        reject(new Error('All promise were rejected'))
                    }
                })
            })
        })
    }
}

module.exports = Promise;